/*******************************************************************************
 * Copyright (c) 2008, 2012 Innoopract Informationssysteme GmbH and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Innoopract Informationssysteme GmbH - initial API and implementation
 *    EclipseSource - ongoing development
 ******************************************************************************/
package org.eclipse.rap.ui.internal.preferences;

import java.io.IOException;

import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.internal.util.ParamCheck;
import org.eclipse.rap.rwt.service.SettingStore;
import org.eclipse.rap.rwt.service.SettingStoreEvent;
import org.eclipse.rap.rwt.service.SettingStoreListener;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.osgi.service.prefs.Preferences;


/**
 * This class is the link between the SessionPreferenceNode hierarchy
 * (application global) the RWT setting store (session specific).
 */
final class SessionPreferenceNodeCore {

  private final SessionPreferencesNode node;
  private ListenerList prefListeners; // ListenerList is thread safe
  private final ListenerList nodeListeners
    = new ListenerList( ListenerList.IDENTITY ); // thread safe

  /* tracks changes in RWT setting store and notifies the prefListeners */
  private SettingStoreListener rwtListener;
  /* true to track RWT changes */
  private boolean trackChanges = true;
  /* ignore changes to this key for a short time */
  private String ignoreKey;

  SessionPreferenceNodeCore( final SessionPreferencesNode node ) {
    ParamCheck.notNull( node, "node" ); //$NON-NLS-1$
    this.node = node;
  }

  void addPreferenceChangeListener( IPreferenceChangeListener listener ) {
    if( listener != null ) {
      getListenerList().add( listener );
      setTrackRWTChanges( true );
    }
  }

  void removePreferenceChangeListener( IPreferenceChangeListener listener ) {
    if( listener != null ) {
      ListenerList list = getListenerList();
      list.remove( listener );
      if( list.isEmpty() ) {
        setTrackRWTChanges( false );
      }
    }
  }

  void firePreferenceEvent( final String key,
                            final String oldValue,
                            final String newValue )
  {
    if( prefListeners != null ) {
      final PreferenceChangeEvent event
        = new PreferenceChangeEvent( node, key, oldValue, newValue );
      Object[] listeners = prefListeners.getListeners();
      for( int i = 0; i < listeners.length; i++ ) {
        final IPreferenceChangeListener listener
          = ( IPreferenceChangeListener )listeners[ i ];
        ISafeRunnable op = new ISafeRunnable() {
          public void handleException( final Throwable exception ) {
            // logged by SafeRunner
          }
          public void run() throws Exception {
            listener.preferenceChange( event );
          }
        };
        SafeRunner.run( op );
      }
    }
  }

  void clear() {
    if( prefListeners != null ) {
      prefListeners.clear();
      prefListeners = null;
      setTrackRWTChanges( false );
    }
    nodeListeners.clear();
  }

  synchronized String put( final String uniqueKey,
                           final String value ) {
    SettingStore store = RWT.getSettingStore();
    String result = store.getAttribute( uniqueKey );
    try {
      ignoreKey = uniqueKey;
      store.setAttribute( uniqueKey, value );
      ignoreKey = null;
    } catch( IOException exception ) {
      String msg = "Could not persist preference: " + uniqueKey; //$NON-NLS-1$
      WorkbenchPlugin.log( msg, exception );
    }
    return result;
  }

  // helping methods
  //////////////////

  private synchronized ListenerList getListenerList() {
    if( prefListeners == null ) {
      prefListeners = new ListenerList( ListenerList.IDENTITY );
    }
    return prefListeners;
  }

  private synchronized void setTrackRWTChanges( final boolean doTrack ) {
    trackChanges = doTrack;
    if( trackChanges ) {
      if( rwtListener == null ) {
       rwtListener = new SettingStoreListener() {
        public void settingChanged( SettingStoreEvent event ) {
          if( trackChanges ) {
            String fullKey = event.getAttributeName();
            if( !fullKey.equals( ignoreKey ) ) {
              String absPath = node.absolutePath();
              if( fullKey.startsWith( absPath ) ) {
                String key = fullKey.substring( absPath.length() + 1 );
                String oldValue = event.getOldValue();
                String newValue = event.getNewValue();
                firePreferenceEvent( key, oldValue, newValue );
              }
            }
          }
        }
       };
     }
     RWT.getSettingStore().addSettingStoreListener( rwtListener );
   } else { // !trackChanges
     if( rwtListener != null ) {
       RWT.getSettingStore().removeSettingStoreListener( rwtListener );
     }
   }
  }

  public void addNodeChangeListener( final INodeChangeListener listener ) {
    nodeListeners.add( listener );
  }

  public void removeNodeChangeListener( final INodeChangeListener listener ) {
    nodeListeners.remove( listener );
  }

  public void fireNodeEvent( final Preferences child,
                             final boolean wasAdded,
                             final SessionPreferencesNode spn )
  {
    final NodeChangeEvent event = new NodeChangeEvent( spn, child );
    Object[] listeners = nodeListeners.getListeners();
    for( int i = 0; i < listeners.length; i++ ) {
      final INodeChangeListener listener
        = ( INodeChangeListener )listeners[ i ];
      ISafeRunnable op = new ISafeRunnable() {
        public void handleException( final Throwable exception ) {
          // logged by SafeRunner
        }
        public void run() throws Exception {
          if( wasAdded ) {
            listener.added( event );
          } else {
            listener.removed( event );
          }
        }
      };
      SafeRunner.run( op );
    }

  }

}
